//
//  BTLEManager.swift
//  TimeBoat
//
//  Created by Jun on 2022/9/6.
//  Copyright © 2022 Time Boat. All rights reserved.
//

import Foundation
import CoreBluetooth
import UIKit
import CoreMedia

@objc protocol BTLEManagerDelegate: NSObjectProtocol {
    @objc func discoverPeripheralValid(_ peripheral: CBPeripheral) -> Bool
    @objc func peripheral(_ peripheral: CBPeripheral, serviceValid service: CBService) -> Bool
    @objc func peripheral(_ peripheral: CBPeripheral, characteristicNotify characteristic: CBCharacteristic) -> Bool
    @objc optional func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral)
    @objc optional func centralManager(_ central: CBCentralManager, didFailToConnect peripheral: CBPeripheral)
    @objc optional func centralManager(_ central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral)
    @objc optional func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic)
    @objc optional func peripheral(_ peripheral: CBPeripheral, didWriteValueFor characteristic: CBCharacteristic)
}

@objc class BTLEManager: NSObject {
    @objc static let shared = BTLEManager()
    @objc private let DEFAULT_NAME: String = "UNKNOWN"
    private let tag = LogTag.init(rawValue: "BTLEManager")
    
    public var mCentralManager: CBCentralManager?
    public let mNotificationCenter = NotificationCenter.default
    public var mUUIDList: [CBUUID] = []
    public var mPerpheralList: [CBPeripheralItem] = []
    public var mConnectPeripheralItem: CBPeripheralItem?
    public weak var delegate: BTLEManagerDelegate?
    public var mConnectionState: CBPeripheralState { get { self.mConnectPeripheralItem?.peripheral?.state ?? .disconnected } }
    
    public func startSearch(services: [CBUUID]? = nil) {
        Log.debug("start search")
        mUUIDList = services ?? []
        clearPerpheralList()
        mCentralManager = CBCentralManager.init(delegate: self, queue: nil)
        // mCentralManager?.scanForPeripherals(withServices: nil)
        
        mNotificationCenter.postBTLEBeginSearchPeripheral(object: self)
    }
    
    public func stopSearch() {
        Log.debug("stop search")
        clearPerpheralList()
        mCentralManager?.stopScan()
        
        mNotificationCenter.postBTLEStopSearchPeripheral(object: self)
    }
    
    public func clearPerpheralList() {
        mPerpheralList.removeAll()
    }
    
    public func connectPeripheralItem(item: CBPeripheralItem?) {
        if (nil != mConnectPeripheralItem) {
            mCentralManager?.cancelPeripheralConnection(mConnectPeripheralItem!.peripheral!)
        }
        
        mConnectPeripheralItem = item
        
        stopSearch()
        if (nil != item?.peripheral) {
            mCentralManager?.connect(item!.peripheral!, options: nil)
            item?.peripheral?.delegate = self
        }
    }
    
    public func disconnectPeripheralItem(item: CBPeripheralItem?) {
        if CBPeripheralState.connected == item?.peripheral?.state {
            mCentralManager?.cancelPeripheralConnection(item!.peripheral!)
        }
    }
    
    public func disconnect() {
        disconnectPeripheralItem(item: mConnectPeripheralItem)
    }
    
    public func invalidCentralManager() {
        mCentralManager?.stopScan()
        mCentralManager = nil
    }
}

extension BTLEManager: CBCentralManagerDelegate {
    /// 状态更新
    func centralManagerDidUpdateState(_ central: CBCentralManager) {
        Log.debug(tag: tag, "central manager did update state = \(central.state)")
        
        NotificationCenter.default.postBTLEDidUpdateStatePeripheral(object: self, info: central.state)
        if CBManagerState.poweredOn == mCentralManager?.state {
            mCentralManager?.scanForPeripherals(withServices: mUUIDList, options: nil)
        }
    }
    
    /// 搜索到设备
    func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
        var isExist = false
        let name = peripheral.name ?? DEFAULT_NAME
        
        Log.debug(tag: tag, "search device name = " + name + ", " + peripheral.identifier.uuidString)
        
        if (nil != delegate && !(delegate!.discoverPeripheralValid(peripheral))) {
            Log.debug("don't add this device   = \(peripheral.name)")
            return
        }
        
        var mac = ""
        let data = advertisementData["kCBAdvDataManufacturerData"] as? NSData
        if nil != data {
            let d = data!
            var buffer = [UInt8](repeating: 0, count: d.length)
            d.getBytes(&buffer, length: d.length)
            
            var i = 2
            while i < 8{
                if mac.count > 0 {
                    mac.append(":")
                }
                
                mac.append(String.init(format: "%02X", buffer[i]))
                i += 1
            }
            
            Log.debug(tag: tag, "mac = \(mac)")
        }
        
        let item = CBPeripheralItem.init(name: name, peripheral: peripheral)
        item.advertisementData = advertisementData
        item.rssi = RSSI.intValue
        item.macAddress = mac
        
        var old: String? = nil
        for temp in mPerpheralList {
            old = temp.peripheral?.identifier.uuidString
            if nil != old && old!.elementsEqual(peripheral.identifier.uuidString) {
                temp.name = name
                temp.peripheral = peripheral
                temp.rssi = RSSI.intValue
                temp.advertisementData = advertisementData
                temp.serviceList.removeAll()
                temp.macAddress = mac
                isExist = true
                
                break;
            }
        }
        
        if !isExist {
            mPerpheralList.append(item)
        }
        
        NotificationCenter.default.postBTLEDidDiscoverPeripheral(object: self, info: peripheral)
    }
    
    /// 连接成功
    func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
        Log.debug(tag: tag, "did connect success")
        
        delegate?.centralManager?(central, didConnect: peripheral)
        NotificationCenter.default.postBTLEDidConnectedPeripheral(object: self, info: peripheral)
        mConnectPeripheralItem?.serviceList.removeAll()
        peripheral.discoverServices(nil)
    }
    
    /// 连接失败
    func centralManager(_ central: CBCentralManager, didFailToConnect peripheral: CBPeripheral, error: Error?) {
        Log.debug(tag: tag, "did fail to connect, error = \(error!)")
        
        delegate?.centralManager?(central, didFailToConnect: peripheral)
        NotificationCenter.default.postBTLEDidConnectFailPeripheral(object: self, info: peripheral)
        mConnectPeripheralItem = nil
    }
    
    /// 断开连接
    func centralManager(_ central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: Error?) {
        Log.debug(tag: tag, "did disconnect")
        
        delegate?.centralManager?(central, didDisconnectPeripheral: peripheral)
        NotificationCenter.default.postBTLEDidDisconnectPeripheral(object: self, info: peripheral)
    }
}

extension BTLEManager: CBPeripheralDelegate {
    func peripheralDidUpdateName(_ peripheral: CBPeripheral) {
        Log.debug(tag: tag, "did update name")
    }
    
    func peripheral(_ peripheral: CBPeripheral, didModifyServices invalidatedServices: [CBService]) {
        Log.debug(tag: tag, "did modify services")
    }
    
    func peripheralDidUpdateRSSI(_ peripheral: CBPeripheral, error: Error?) {
        Log.debug(tag: tag, "did update RSSI, error = \(String(describing: error))")
    }
    
    func peripheral(_ peripheral: CBPeripheral, didReadRSSI RSSI: NSNumber, error: Error?) {
        Log.debug(tag: tag, "did read RSSI, \(String(describing: error))")
    }
    
    func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {
        Log.debug(tag: tag, "did discover services, \(String(describing: error))")
        
        let services = peripheral.services
        if (nil != services) {
            for service in services! {
                if (nil != delegate) && (delegate!.peripheral(peripheral, serviceValid: service)) {
                    peripheral.discoverCharacteristics(nil, for: service)
                }
            }
        }
    }
    
    func peripheral(_ peripheral: CBPeripheral, didDiscoverIncludedServicesFor service: CBService, error: Error?) {
        Log.debug(tag: tag, "did discover included service, \(String(describing: error))")
    }
    
    func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
        Log.debug(tag: tag, "did discover characteristics, \(String(describing: error))")
        
        let list = service.characteristics
        if nil != list {
            for item in list! {
                if (nil != delegate) && (delegate!.peripheral(peripheral, characteristicNotify: item)) {
                    peripheral.setNotifyValue(true, for: item)
                }
            }
        }
    }
    
    func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
        Log.debug(tag: tag, "did update value for characteristic, \(String(describing: error))")
        delegate?.peripheral?(peripheral, didUpdateValueFor: characteristic)
    }
    
    func peripheral(_ peripheral: CBPeripheral, didWriteValueFor characteristic: CBCharacteristic, error: Error?) {
        Log.debug(tag: tag, "did write value for characteristic, \(String(describing: error))")
        delegate?.peripheral?(peripheral, didWriteValueFor: characteristic)
    }
    
    func peripheral(_ peripheral: CBPeripheral, didUpdateNotificationStateFor characteristic: CBCharacteristic, error: Error?) {
        Log.debug(tag: tag, "did update notification state, \(String(describing: error))")
    }
    
    func peripheral(_ peripheral: CBPeripheral, didDiscoverDescriptorsFor characteristic: CBCharacteristic, error: Error?) {
        Log.debug(tag: tag, "did discover descriptors, \(String(describing: error))")
    }
    
    func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor descriptor: CBDescriptor, error: Error?) {
        Log.debug(tag: tag, "did update value for descriptor, \(String(describing: error))")
    }
    
    func peripheral(_ peripheral: CBPeripheral, didWriteValueFor descriptor: CBDescriptor, error: Error?) {
        Log.debug(tag: tag, "did write value for descriptor, \(String(describing: error))")
    }
}
